package net.sourceforge.openforecast;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import java.util.TreeSet;

/** Represents a collection of data points. Data points are either observations of past data (including both the values
 * of the independent variables and the observed value of the dependent variable), or forecasts or estimates of the
 * dependent variable (for a given set of independent variable values).
 * <p>
 * Generally when trying to forecast future values you'll use two data sets. The first data set contains all of the
 * observations, or historical data. This data set is used to help initialize the selected forecasting model, the
 * details of which depend on the specific forecasting model. A second data set is then created and initialized with
 * data points describing the values of the independent variables that are to be used to predict or forecast values of
 * the dependent variable.
 * <p>
 * When defining any data set it is important to provide as much information as possible about the data. While on the
 * surface it may seem trivial, the more information you can provide about a data set (such as whether it is a
 * time-based series, the name of the independent variable representing time, the number of data points/periods in a
 * year), the better the forecasting model will be able to model the data. This is because some models need this type of
 * data to even be applicable. */
public class DataSet extends ArrayList<DataPoint> {
	private static final long serialVersionUID = 1L;

	/** If this data set is a time-based series, then for best results the timeVariable should be initialized to contain
	 * the name of the time variable. If set to null, the data set is treated as being non time-based. */
	private String timeVariable;

	/** For time-series data, periodsPerYear should be initialized to the number of periods - or data points - in a
	 * years worth of data. */
	private int periodsPerYear;

	/** Constructs a new empty data set. */
	public DataSet() {
		timeVariable = null;
		periodsPerYear = 0;
	}

	/** Copy constructor: constructs a new data set object by copying the given data set.
	 * 
	 * @param dataSet the data set to copy from to initialize the new data set. */
	public DataSet(DataSet dataSet) {
		this(dataSet.getTimeVariable(), dataSet.getPeriodsPerYear(), dataSet);
	}

	/** Constructs a new time-based data set with the named time variable, the given number of data points in a year,
	 * and the given Collection of data points. This is equivalent to using the default constructor, then calling
	 * setTimeVariable, setPeriodsPerYear and addAll to initialize it.
	 * 
	 * @param timeVariable the name of the independent variable representing time.
	 * @param periodsPerYear the number of periods - data points - in one years worth of data.
	 * @param c a Collection of data points to initialize this data set with.
	 * @see #setTimeVariable
	 * @see #setPeriodsPerYear
	 * @see #addAll */
	public DataSet(String timeVariable, int periodsPerYear, Collection<DataPoint> c) {
		this.timeVariable = timeVariable;
		this.periodsPerYear = periodsPerYear;

		addAll(c);
	}

	/** Returns the time variable associated with this data set, or <code>null</code> if no time variable has been
	 * defined.
	 * 
	 * @return the time variable associated with this data set. */
	public String getTimeVariable() {
		return timeVariable;
	}

	/** Sets the name of the time variable for this data set. If this is not set, then the data set will be treated as
	 * being non time-based. In addition to setting the time variable for time series data, it is strongly recommended
	 * that you also initialize the number of periods per year with a call to setPeriodsPerYear.
	 * 
	 * @param timeVariable the name of the independent variable that represents the time data component. For example,
	 *        this may be something like "t", "month", "period", "year", and so on.
	 * @see #setPeriodsPerYear */
	public void setTimeVariable(String timeVariable) {
		this.timeVariable = timeVariable;
	}

	/** Returns the number of periods - or data points - in a years worth of data for time-series data. If this has not
	 * been set, then a value of 0 will be returned.
	 * 
	 * @return the number of periods in a years worth of data. */
	public int getPeriodsPerYear() {
		return periodsPerYear;
	}

	/** Sets the number of periods - or data points - in a years worth of data for time-series data. If this is not set,
	 * then no seasonality effects will be considered when forecasting using this data set.
	 * <p>
	 * In addition to setting the number of periods per year, you must also set the time variable otherwise any
	 * forecasting model will not be able to consider the potential effects of seasonality.
	 * 
	 * @param periodsPerYear the number of periods in a years worth of data.
	 * @see #setTimeVariable */
	public void setPeriodsPerYear(int periodsPerYear) {
		if (periodsPerYear < 1)
			throw new IllegalArgumentException("periodsPerYear parameter must be at least 1");

		this.periodsPerYear = periodsPerYear;
	}

	/** Returns an ordered array of all independent variable names used in this data set. The array is guaranteed not to
	 * contain duplicate names.
	 * 
	 * @return a sorted array of unique independent variable names for this data set. */
	public Set<String> getNames() {
		Set<String> names = new TreeSet<>();

		forEach(x -> names.addAll(x.getNames()));

		return names;
	}

	/** Overrides the default toString method. Lists all data points in this data set. Note that if there are a large
	 * number of data points in this data set, then the String returned could be very long.
	 * 
	 * @return a string representation of this data set. */
	// @Override
	// public String toString() {
	// StringBuffer sb = new StringBuffer();
	//
	// forEach(x -> sb.append(x.toString())
	// .append(System.lineSeparator()));
	//
	// return sb.toString();
	// }
}